/* Esptouch example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/

/*
esp32的wifi自动配网
两种模式：
1、ESP_ERROR_CHECK(esp_smartconfig_set_type(SC_TYPE_ESPTOUCH));    //仅能使用esptouch的手机app配置
2、ESP_ERROR_CHECK(esp_smartconfig_set_type(SC_TYPE_ESPTOUCH_AIRKISS));    //可以使用微信小程序配置
配网一旦结束，则自删除配网任务
*/

#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_wpa2.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_smartconfig.h"

/* FreeRTOS event group to signal when we are connected & ready to make a request */
static EventGroupHandle_t s_wifi_event_group;

/* The event group allows multiple bits for each event,
   but we only care about one event - are we connected
   to the AP with an IP? */
static const int CONNECTED_BIT = BIT0;
static const int ESPTOUCH_DONE_BIT = BIT1;
static const char *TAG = "smartconfig";

static void smartconfig_example_task(void *parm);

//保存wifi配置参数结构体变量wifi_config到nvs------------------------------
static void saveWifiConfig(wifi_config_t *wifi_config)
{
    nvs_handle my_handle;
    nvs_open("WIFI_CONFIG", NVS_READWRITE, &my_handle); //打开

    ESP_ERROR_CHECK(nvs_set_blob(my_handle, "wifi_config", wifi_config, sizeof(wifi_config_t)));

    ESP_ERROR_CHECK(nvs_commit(my_handle)); //提交
    nvs_close(my_handle);                   //退出
}

//从nvs中读取wifi配置到给定的sta_config结构体变量
esp_err_t readWifiConfig(wifi_config_t *sta_config)
{
    nvs_handle my_handle;
    nvs_open("WIFI_CONFIG", NVS_READWRITE, &my_handle); //打开

    // sta_config = {
    //     .sta = {
    //         .ssid = "ssid",
    //         .password = "password",
    //         .bssid_set = false}}

    uint32_t len = sizeof(wifi_config_t);
    esp_err_t err = nvs_get_blob(my_handle, "wifi_config", sta_config, &len);

    nvs_close(my_handle); //退出

    return err;
}

//事件处理函数--------------------------------------------------------
static void event_handler(void *arg, esp_event_base_t event_base,
                          int32_t event_id, void *event_data)
{
    //初始状态，启动智能配网任务
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
    {
        xTaskCreate(smartconfig_example_task, "smartconfig_example_task", 4096, NULL, 3, NULL);
    }
    //如果连接ap失败，事件状态位会变为WIFI_EVENT_STA_DISCONNECTED，继续连接，并清除连接标志
    else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
    {
        esp_wifi_connect();
        xEventGroupClearBits(s_wifi_event_group, CONNECTED_BIT);
    }
    //在拿到IP之后事件状态会变更为IP_EVENT_STA_GOT_IP。在这里我们使用了xEventGroupSetBits，设置事件组标志，
    else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
    {
        xEventGroupSetBits(s_wifi_event_group, CONNECTED_BIT);
    }
    //扫描完成
    else if (event_base == SC_EVENT && event_id == SC_EVENT_SCAN_DONE)
    {
        ESP_LOGI(TAG, "Scan done");
    }
    //扫描后发现wifi信道
    else if (event_base == SC_EVENT && event_id == SC_EVENT_FOUND_CHANNEL)
    {
        ESP_LOGI(TAG, "Found channel");
    }
    //获取了wifi的ssid和密码，则尝试连接ap，保存参数到nvs存储
    else if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD)
    {
        ESP_LOGI(TAG, "Got SSID and password");

        //格式化事件传递过来的参数，以便获取ssid和密码
        smartconfig_event_got_ssid_pswd_t *evt = (smartconfig_event_got_ssid_pswd_t *)event_data;
        wifi_config_t wifi_config; //定义一个wifi配置变量
        uint8_t ssid[33] = {0};
        uint8_t password[65] = {0};

        bzero(&wifi_config, sizeof(wifi_config_t));                                        //wifi配置全部清除
        memcpy(wifi_config.sta.ssid, evt->ssid, sizeof(wifi_config.sta.ssid));             //复制ssid到wifi配置变量
        memcpy(wifi_config.sta.password, evt->password, sizeof(wifi_config.sta.password)); //复制密码到wifi配置变量
        wifi_config.sta.bssid_set = evt->bssid_set;                                        //复制是否需要设置目标ap的mac地址到wifi配置变量
        if (wifi_config.sta.bssid_set == true)                                             //如果需要则复制
        {
            memcpy(wifi_config.sta.bssid, evt->bssid, sizeof(wifi_config.sta.bssid));
        }

        memcpy(ssid, evt->ssid, sizeof(evt->ssid));
        memcpy(password, evt->password, sizeof(evt->password));
        ESP_LOGI(TAG, "SSID:%s", ssid);
        ESP_LOGI(TAG, "PASSWORD:%s", password);

        ESP_ERROR_CHECK(esp_wifi_disconnect());                              //断开wifi
        ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config)); //设置wifi的sta参数
        ESP_ERROR_CHECK(esp_wifi_connect());                                 //连接wifi

        //保存wifi配网参数到nvs中
        saveWifiConfig(&wifi_config);
    }
    //获取后应答完成
    else if (event_base == SC_EVENT && event_id == SC_EVENT_SEND_ACK_DONE)
    {
        xEventGroupSetBits(s_wifi_event_group, ESPTOUCH_DONE_BIT); //事件组职位表示智能配网完成
    }
}

//初始化wifi-------------------------------------------------
void initialise_wifi(void)
{
    ESP_ERROR_CHECK(esp_netif_init());                            //底层网络协议初始化
    s_wifi_event_group = xEventGroupCreate();                     //Freertos创建一个事件组
    ESP_ERROR_CHECK(esp_event_loop_create_default());             //执行后台默认事件循环程序，监控wifi、ip、以太网的状态
    esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta(); //创建一个默认的wifi的sta模式
    assert(sta_netif);

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); //赋值默认的wifi初始化配置
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));                //wifi初始化
    //向上面创建的的事件循环中注册事件和事件处理函数
    //注册一个事件句柄到WIFI_EVENT事件，如果发生任何事件（ESP_EVENT_ANY_ID），则执行回调函数event_handler，无额外参数传递
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
    //注册一个事件句柄到IP_EVENT事件，如果发生获取了IP事件（IP_EVENT_STA_GOT_IP），则执行回调函数event_handler，无额外参数传递
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
    //注册一个事件句柄到SC_EVENT事件，如果发生获取了任何SC事件（ESP_EVENT_ANY_ID），则执行回调函数event_handler，无额外参数传递
    ESP_ERROR_CHECK(esp_event_handler_register(SC_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); //wifi设置为sta模式
    ESP_ERROR_CHECK(esp_wifi_start());                 //启动wifi
}

//智能配网任务，配网成功后直接重启----------------------------------------
static void smartconfig_example_task(void *parm)
{
    EventBits_t uxBits;
    //ESP_ERROR_CHECK(esp_smartconfig_set_type(SC_TYPE_ESPTOUCH));    //仅能使用esptouch的手机app配置
    ESP_ERROR_CHECK(esp_smartconfig_set_type(SC_TYPE_ESPTOUCH_AIRKISS)); //可以使用微信小程序配置
    smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT(); //智能配网用默认的参数
    ESP_ERROR_CHECK(esp_smartconfig_start(&cfg));                        //智能配网启动
    while (1)
    {
        //等待连接成功或配网结束的事件到来
        uxBits = xEventGroupWaitBits(s_wifi_event_group, CONNECTED_BIT | ESPTOUCH_DONE_BIT, true, false, portMAX_DELAY);
        if (uxBits & CONNECTED_BIT) //连接成功
        {
            ESP_LOGI(TAG, "WiFi Connected to ap");
        }
        if (uxBits & ESPTOUCH_DONE_BIT) //配网结束
        {
            ESP_LOGI(TAG, "smartconfig over");
            esp_smartconfig_stop(); //结束配网
            //直接重启------------------------
            ESP_LOGI(TAG, "Restart now.\n");
            esp_restart(); //系统软件复位
            //直接重启------------------------
            vTaskDelete(NULL);      //自删除任务
            
        }
    }
}
